/*
 jChecs: a Java chess game sample 

 Copyright (C) 2006-2011 by David Cotton

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package fr.free.jchecs.core;

import java.io.Serializable;

import static fr.free.jchecs.core.Constants.FILE_COUNT;
import static fr.free.jchecs.core.Constants.RANK_COUNT;

/**
 * Description d'une cellule de l'échiquier.
 * <p>
 * Les instances de cette classe sont des <b>singletons immuables</b> : classe
 * sûre vis-à-vis des threads et permettant des comparaisons directes sur les
 * références d'objets.
 * </p>
 * 
 * @author David Cotton
 */
public final class Square implements Comparable<Square>, Serializable {
	/** Identifiant de la classe pour la sérialisation. */
	private static final long serialVersionUID = -5504534226984844152L;

	/** Liste des coordonnées de cellules. */
	private static final Square[] SQUARES;
	static {
		SQUARES = new Square[FILE_COUNT * RANK_COUNT];
		int i = 0;
		for (int y = 0; y < RANK_COUNT; y++) {
			for (int x = 0; x < FILE_COUNT; x++) {
				SQUARES[i++] = new Square(x, y);
			}
		}
	}

	/** Colonne. */
	private final int _file;

	/** Ligne. */
	private final int _rank;

	/** Indice de la cellule. */
	private final transient int _index;

	/** Chaine de description de la cellule. */
	private final transient String _string;

	/** Chaine FEN identifiant la cellule. */
	private final transient String _fenString;

	/**
	 * Instancie une nouvelle coordonnée.
	 * 
	 * @param pColonne
	 *            Colonne de la cellule (de 0 à 7).
	 * @param pLigne
	 *            Ligne de la colonne (de 0 à 7).
	 */
	private Square(final int pColonne, final int pLigne) {
		assert (pColonne >= 0) && (pColonne < FILE_COUNT);
		assert (pLigne >= 0) && (pLigne < RANK_COUNT);

		_file = pColonne;
		_rank = pLigne;
		_index = pColonne + pLigne * RANK_COUNT;

		final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
		sb.append("[file=").append((char) ('a' + getFile()));
		sb.append(",rank=").append((char) ('1' + getRank()));
		sb.append(']');
		_string = sb.toString();

		sb.delete(0, sb.length());
		sb.append((char) ('a' + getFile())).append((char) ('1' + getRank()));
		_fenString = sb.toString();
	}

	/**
	 * Fixe l'ordre de tri entre les cases.
	 * 
	 * @param pCase
	 *            Case avec laquelle comparer.
	 * @return -1 si pCase est inférieure, 0 si égale et 1 si supérieure.
	 * @see Comparable#compareTo(Object)
	 */
	@Override
	public int compareTo(final Square pCase) {
		if (pCase == null) {
			throw new NullPointerException();
		}

		return getFENString().compareTo(pCase.getFENString());
	}

	/**
	 * Teste l'égalité entre deux descriptions de cellules.
	 * 
	 * @param pObjet
	 *            Objet avec lequel comparer.
	 * @return Vrai si les deux objets sont égaux.
	 */
	@Override
	public boolean equals(final Object pObjet) {
		if (pObjet == this) {
			return true;
		}

		if (!(pObjet instanceof Square)) {
			return false;
		}

		final Square o = (Square) pObjet;
		return _index == o._index;
	}

	/**
	 * Renvoi la chaine FEN identifiant la cellule.
	 * 
	 * @return Identifiant FEN.
	 */
	public String getFENString() {
		assert _fenString != null;
		return _fenString;
	}

	/**
	 * Renvoi la colonne de la cellule.
	 * 
	 * @return Colonne de la cellule.
	 */
	public int getFile() {
		assert (_file >= 0) && (_file < FILE_COUNT);
		return _file;
	}

	/**
	 * Renvoi l'indice de la cellule.
	 * 
	 * @return Indece de la cellule (entre 0 et 63).
	 */
	public int getIndex() {
		assert (_index >= 0) && (_index < FILE_COUNT * RANK_COUNT);
		return _index;
	}

	/**
	 * Renvoi la ligne de la cellule.
	 * 
	 * @return Ligne de la cellule.
	 */
	public int getRank() {
		assert (_rank >= 0) && (_rank < RANK_COUNT);
		return _rank;
	}

	/**
	 * Surcharge du calcul de la clé de hachage.
	 * 
	 * @return Clé de hachage.
	 */
	@Override
	public int hashCode() {
		return _index;
	}

	/**
	 * Résout la désérialisation d'un objet pour en garantir le comportement
	 * "singleton".
	 * 
	 * @return Instance correspondante dans la JVM.
	 */
	private Object readResolve() {
		return valueOf(getFile(), getRank());
	}

	/**
	 * Renvoi une chaine représentant la cellule.
	 * 
	 * @return Chaine représentant la case.
	 */
	@Override
	public String toString() {
		assert _string != null;
		return _string;
	}

	/**
	 * Renvoi l'instance correspondant à un indice.
	 * 
	 * @param pIndice
	 *            Indice de la case (entre 0 et 63).
	 * @return Instance correspondante.
	 */
	public static Square valueOf(final int pIndice) {
		assert (pIndice >= 0) && (pIndice < FILE_COUNT * RANK_COUNT);

		return SQUARES[pIndice];
	}

	/**
	 * Renvoi l'instance correspondant à une notation FEN.
	 * 
	 * @param pChaine
	 *            Chaine FEN décrivant la case.
	 * @return Instance correspondante.
	 */
	public static Square valueOf(final String pChaine) {
		if (pChaine == null) {
			throw new NullPointerException("Missing square string");
		}
		if (pChaine.length() != 2) {
			throw new IllegalArgumentException("Illegal square string ["
					+ pChaine + ']');
		}

		return valueOf(pChaine.charAt(0) - 'a', pChaine.charAt(1) - '1');
	}

	/**
	 * Renvoi l'instance correspondant à une cellule.
	 * 
	 * @param pColonne
	 *            Colonne de la cellule (de 0 à 7).
	 * @param pLigne
	 *            Ligne de la colonne (de 0 à 7).
	 * @return Instance correspondante.
	 */
	public static Square valueOf(final int pColonne, final int pLigne) {
		if ((pColonne < 0) || (pColonne >= FILE_COUNT)) {
			throw new IllegalArgumentException("Illegal file [" + pColonne
					+ ']');
		}
		if ((pLigne < 0) || (pLigne >= RANK_COUNT)) {
			throw new IllegalArgumentException("Illegal rank [" + pLigne + ']');
		}

		return SQUARES[pColonne + pLigne * FILE_COUNT];
	}

	/**
	 * Renvoi la liste des cases.
	 * 
	 * @return Liste des cases.
	 */
	public static Square[] values() {
		final Square[] res = new Square[FILE_COUNT * RANK_COUNT];
		System.arraycopy(SQUARES, 0, res, 0, FILE_COUNT * RANK_COUNT);

		return res;
	}
}
